import { Tabs } from "nextra/components";
import UniversalTabs from "../../../../components/UniversalTabs";

# The GROUP_ROUND_ROBIN Concurrency Limit Strategy in Hatchet

Hatchet's `GROUP_ROUND_ROBIN` concurrency limit strategy is an advanced way to manage resource contention in your workflows while ensuring fair distribution of resources across different groups of tenants, users, or other concurrency key. This strategy allows you to process workflow instances in a round-robin fashion within each group, as defined by a key function.

## How it works

When a new workflow instance is triggered, the `GROUP_ROUND_ROBIN` strategy will:

1. Determine the group that the instance belongs to based on the `key` function defined in the workflow's concurrency configuration.
2. Check if there are any available slots for the instance's group based on the `maxRuns` limit of available workers.
3. If a slot is available, the new workflow instance starts executing immediately.
4. If no slots are available, the new workflow instance is added to a queue for its group.
5. When a running workflow instance completes and a slot becomes available for a group, the next queued instance for that group (in round-robin order) is dequeued and starts executing.

This strategy ensures that workflow instances are processed fairly across different groups, preventing any one group from monopolizing the available resources. It also helps to reduce latency for instances within each group, as they are processed in a round-robin fashion rather than strictly in the order they were triggered.

## When to use GROUP_ROUND_ROBIN

The `GROUP_ROUND_ROBIN` strategy is particularly useful in scenarios where:

- You have multiple clients or users triggering workflow instances, and you want to ensure fair resource allocation among them.
- You want to process instances within each group in a round-robin fashion to minimize latency and ensure that no single instance within a group is starved for resources.
- You have long-running workflow instances and want to avoid one group's instances monopolizing the available slots.

Keep in mind that the `GROUP_ROUND_ROBIN` strategy may not be suitable for all use cases, especially those that require strict ordering or prioritization of the most recent events.

## How to use GROUP_ROUND_ROBIN

To use the `GROUP_ROUND_ROBIN` concurrency limit strategy, define a `concurrency` configuration in your workflow definition:

<Tabs items={["CEL Expression (Recommended)", "Key Function (Advanced)"]}>
  <Tabs.Tab>

CEL expressions are evaluated on the Hatchet engine (not in your worker) and can be used to extract values from the workflow input or [additional metadata](/features/additional-metadata).

<UniversalTabs items={['Python', 'Typescript', 'Go']}>
  <Tabs.Tab>

```python
from hatchet_sdk import ConcurrencyLimitStrategy, ConcurrencyExpression

@hatchet.workflow(
  on_events=["concurrency-test"],
  concurrency=ConcurrencyExpression(
    expression="input.group",
    max_runs=5,
    limit_strategy=ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
  )
)
class ConcurrencyDemoWorkflow:
    @hatchet.step()
    def step1(self, context):
        print("executed step1")
        pass

```

  </Tabs.Tab>
  <Tabs.Tab>

```typescript
export const concurrencyDemoWorkflow: Workflow = {
  id: "my-workflow",
  description: "My workflow with concurrency control",
  on: {
    event: "concurrency-test",
  },
  steps: [
    // ...
  ],
  concurrency: {
    name: "my-workflow-concurrency",
    maxRuns: 10,
    limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
    expression: "input.group",
  },
};
```

  </Tabs.Tab>
  <Tabs.Tab>

```go
import (
  "github.com/hatchet-dev/hatchet/pkg/client/types"
  "github.com/hatchet-dev/hatchet/pkg/worker"
)

type MyUser struct {
    UserId string `json:"user_id"`
}

err = w.RegisterWorkflow(
    &worker.WorkflowJob{
        Name: "concurrency-limit-per-user",
        On: worker.Events("concurrency-test-event"),
        Description: "This limits concurrency to 10 run at a time per user.",
        Concurrency: worker.Concurrency(getConcurrencyKey).MaxRuns(10).LimitStrategy(types.GroupRoundRobin),
        Steps: []*worker.WorkflowStep{
            // your steps here...
        },
    },
)

```

  </Tabs.Tab>
</UniversalTabs>

In this example:

- `maxRuns` sets the maximum number of concurrent instances allowed for each group.
- `limitStrategy` is set to `GROUP_ROUND_ROBIN`, indicating that instances should be processed in a round-robin fashion within each group.
- `expression` is a CEL expression that is evaluated on the Hatchet engine (not in your worker) and can be used to extract values from the workflow input or [additional metadata](/features/additional-metadata).

</Tabs.Tab>
  <Tabs.Tab>
In advanced use cases, you may want to use a custom function to determine the concurrency key. Concurrency key functions are evaluated on your Hatchet workers and can be used to extract values from the workflow input, context, and other sources (i.e. database, external API, etc.).
<UniversalTabs items={['Python', 'Typescript', 'Go']}>

  <Tabs.Tab>

```python
from hatchet_sdk import ConcurrencyLimitStrategy

@hatchet.workflow(on_events=["concurrency-test"])
class ConcurrencyDemoWorkflow:
    @hatchet.concurrency(max_runs=10, limit_strategy=ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN)
    def concurrency(self, context) -> str:
        return context.workflow_input()["user_id"]

    @hatchet.step()
    def step1(self, context):
        print("executed step1")
        pass

```

  </Tabs.Tab>
  <Tabs.Tab>

```typescript
export const concurrencyDemoWorkflow: Workflow = {
  id: "my-workflow",
  description: "My workflow with concurrency control",
  on: {
    event: "concurrency-test",
  },
  steps: [
    // ...
  ],
  concurrency: {
    name: "my-workflow-concurrency",
    maxRuns: 10,
    limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
    key: (ctx) => ctx.workflowInput().userId,
  },
};
```

  </Tabs.Tab>
  <Tabs.Tab>

```go
import (
  "github.com/hatchet-dev/hatchet/pkg/client/types"
  "github.com/hatchet-dev/hatchet/pkg/worker"
)

type MyUser struct {
    UserId string `json:"user_id"`
}

func getConcurrencyKey(ctx worker.HatchetContext) (string, error) {
  event := &MyUser{}
  err := ctx.WorkflowInput(event)

    if err != nil {
        return "", err
    }

    return event.UserId, nil

}

err = w.RegisterWorkflow(
    &worker.WorkflowJob{
        Name: "concurrency-limit-per-user",
        On: worker.Events("concurrency-test-event"),
        Description: "This limits concurrency to 10 run at a time per user.",
        Concurrency: worker.Concurrency(getConcurrencyKey).MaxRuns(10).LimitStrategy(types.GroupRoundRobin),
        Steps: []*worker.WorkflowStep{
            // your steps here...
        },
    },
)

```

  </Tabs.Tab>
</UniversalTabs>

In this example:

- `maxRuns` sets the maximum number of concurrent instances allowed for each group.
- `limitStrategy` is set to `GROUP_ROUND_ROBIN`, indicating that instances should be processed in a round-robin fashion within each group.
- `key` is a function that takes the workflow context and returns a string key. This key is used to group workflow instances for the purpose of concurrency limiting and round-robin processing. In this example, instances are grouped by `userId`, so the concurrency limit and round-robin processing are applied separately for each unique user.

</Tabs.Tab>
</Tabs>

> NOTE: Only one of `key` or `expression` should be provided, not both.

With this configuration, Hatchet will automatically manage your workflow's concurrency, processing instances within each group in a round-robin fashion and ensuring fair distribution of resources across groups.
